package com.rs.lc.pm.filter;

import com.alibaba.fastjson.JSON;
import com.rs.lc.pm.constans.ReturnCode;
import com.rs.lc.pm.model.user.User;
import com.rs.lc.pm.model.utils.JsonResponse;
import com.rs.lc.pm.security.UserThreadLocal;
import com.rs.lc.pm.service.user.UserService;
import com.rs.lc.pm.utils.CookieUtil;
import com.rs.lc.pm.utils.SpringBeanUtil;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by Xavier_Wei on 16/8/11.
 * 判断用户访问权限的Filter
 */
public class ApiAccessFilter implements Filter {


    public static Set<String> ignoreUrl = new HashSet<>();
    public static UserService userService;

    static {
        ignoreUrl.add("/api/user/login");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        RequestWrapper wrappedRequest = new RequestWrapper(req);
        ResponseWrapper wrappedResponse = new ResponseWrapper(resp);
        String url = req.getRequestURL().toString();
        User user = fillUserIntoThreadFromCookie(req);
        if (StringUtils.isNotBlank(url)) {
            url = url.substring(url.indexOf("api"));
            if (isIgnoreUrl(url) || user != null) {
                chain.doFilter(wrappedRequest, wrappedResponse);
            } else {
                fillResponseWithError(wrappedResponse);
                return;
            }
        }
    }

    public static UserService getUserService() {
        if (userService != null) {
            return userService;
        } else {
            return SpringBeanUtil.getBean(UserService.class, "userService");
        }
    }

    private User fillUserIntoThreadFromCookie(HttpServletRequest request) {
        String[] userInfo = CookieUtil.getUserCookieDecodeValue(request);
        UserThreadLocal.setThreadLocal(null);
        User user = null;
        if (ArrayUtils.isNotEmpty(userInfo) && userInfo.length == 4) {
            if ("phone".equals(userInfo[0]) && "password".equals(userInfo[2])) {
                user = getUserService().getByPhoneAndPassword(userInfo[1], userInfo[3]);
            }
            if ("email".equals(userInfo[0]) && "password".equals(userInfo[2])) {
                //TODO getByEmailAndPassword
            }
        }
        UserThreadLocal.setThreadLocal(user);
        return user;
    }

    private boolean isIgnoreUrl(String url) {
        for (String singlUrl : ignoreUrl) {
            if (singlUrl.endsWith(url)) return true;
        }
        return false;
    }

    private void fillResponseWithError(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding("UTF-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        JsonResponse jsonResponse = JsonResponse.create();
        jsonResponse.setStatus(ReturnCode.USER_NO_LOGIN_CODE);
        jsonResponse.setErrorMsg(ReturnCode.USER_NO_LOGIN_MSG);
        response.getWriter().print(JSON.toJSONString(jsonResponse));
    }

    @Override
    public void destroy() {
    }


    public class RequestWrapper extends HttpServletRequestWrapper {

        private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

        public RequestWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new ServletInputStream() {
                private TeeInputStream tee = new TeeInputStream(RequestWrapper.super.getInputStream(), bos);

                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }

                @Override
                public int read() throws IOException {
                    return tee.read();
                }
            };
        }

        public String getBody() {
            try {
                return bos.toString("UTF-8");
            } catch (UnsupportedEncodingException e) {
                return "";
            }
        }
    }

    public class ResponseWrapper extends HttpServletResponseWrapper {

        ByteArrayServletStream byteArrayServletStream;
        ByteArrayPrintWriter byteArrayPrintWriter;

        /**
         * Constructs a response adaptor wrapping the given response.
         *
         * @param response
         * @throws IllegalArgumentException if the response is null
         */
        public ResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            if (!getContentType().contains(MediaType.APPLICATION_JSON_VALUE))
                return super.getWriter();

            if (byteArrayServletStream != null) {
                throw new IllegalStateException("getOutputStream() has already been called on this response.");
            }

            if (byteArrayPrintWriter == null) {
                byteArrayPrintWriter = new ByteArrayPrintWriter(super.getWriter());
//                byteArrayServletStream = new ByteArrayServletStream(getResponse().getOutputStream());
//                pw = new PrintWriter(new OutputStreamWriter(byteArrayServletStream, getCharacterEncoding()), true);
            }

            return byteArrayPrintWriter;
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            if (StringUtils.isEmpty(getContentType()) || !getContentType().contains(MediaType.APPLICATION_JSON_VALUE))
                return super.getOutputStream();

            if (byteArrayPrintWriter != null) {
                throw new IllegalStateException("getWriter() has already been called on this response.");
            }

            if (byteArrayServletStream == null) {
//                outputStream = getResponse().getOutputStream();
                byteArrayServletStream = new ByteArrayServletStream(super.getOutputStream());
            }

            return byteArrayServletStream;
        }

        public byte[] getCopy() {
            if (byteArrayServletStream != null)
                return byteArrayServletStream.toByteArray();
            else if (byteArrayPrintWriter != null)
                return byteArrayPrintWriter.toByteArray();
            return new byte[0];
        }
    }

    private class ByteArrayServletStream extends ServletOutputStream {

        ByteArrayOutputStream baos;
        OutputStream os;

        ByteArrayServletStream(OutputStream outputStream) {
            this.baos = new ByteArrayOutputStream(1024);
            this.os = outputStream;
        }

        @Override
        public void write(int param) throws IOException {
            os.write(param);
            baos.write(param);
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {

        }

        public byte[] toByteArray() {
            return baos.toByteArray();
        }
    }


    private class ByteArrayPrintWriter extends PrintWriter {

        Writer respWriter;
        ByteArrayOutputStream baos;

        public ByteArrayPrintWriter(Writer out) {
            super(out);
            this.respWriter = out;
            this.baos = new ByteArrayOutputStream(1024);
        }


        @Override
        public void print(String s) {
            try {
                respWriter.write(s);
                baos.write(s.getBytes(Charsets.UTF_8));
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        }

        public byte[] toByteArray() {
            return baos.toByteArray();
        }
    }
}
